#include "session_repository.h"
#include <spdlog/spdlog.h>

int SessionRepository::create(Session& session)
{
    PreparedStatement stmt(db_, "INSERT INTO sessions (user_id, token, created_at, expires_at) VALUES (?, ?, ?, ?)");

    // Check statement preparation by trying to bind
    DbError error = stmt.bind_int(1, session.user_id);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to prepare statement or bind user_id: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    error = stmt.bind_text(2, session.token);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to bind token: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    error = stmt.bind_text(3, session.created_at);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to bind created_at: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    error = stmt.bind_text(4, session.expires_at);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to bind expires_at: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    // Execute statement
    error = stmt.step();
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to execute insert statement: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    // Set the session ID from the last inserted row ID
    session.id = static_cast<int>(stmt.last_insert_rowid());

    return REPO_SUCCESS;
}

int SessionRepository::update(const Session& session)
{
    PreparedStatement stmt(db_, "UPDATE sessions SET user_id = ?, token = ?, created_at = ?, expires_at = ? WHERE id = ?");

    // Check statement preparation by trying to bind
    DbError error = stmt.bind_int(1, session.user_id);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to prepare statement or bind user_id: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    error = stmt.bind_text(2, session.token);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to bind token: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    error = stmt.bind_text(3, session.created_at);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to bind created_at: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    error = stmt.bind_text(4, session.expires_at);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to bind expires_at: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    error = stmt.bind_int(5, session.id);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to bind id: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    // Execute statement
    error = stmt.step();
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to execute update statement: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    // Check if any rows were affected
    if (stmt.changes() == 0)
    {
        return REPO_ERROR_DATABASE;  // No rows affected, treat as error
    }

    return REPO_SUCCESS;
}

int SessionRepository::remove(int id)
{
    PreparedStatement stmt(db_, "DELETE FROM sessions WHERE id = ?");

    // Check statement preparation by trying to bind
    DbError error = stmt.bind_int(1, id);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to prepare statement or bind id: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    // Execute statement
    error = stmt.step();
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to execute delete statement: {}", static_cast<int>(error));
        return REPO_ERROR_DATABASE;
    }

    // Check if any rows were affected
    if (stmt.changes() == 0)
    {
        return REPO_ERROR_DATABASE;  // No rows affected, treat as error
    }

    return REPO_SUCCESS;
}

std::optional<Session> SessionRepository::findById(int id)
{
    PreparedStatement stmt(db_, "SELECT id, user_id, token, created_at, expires_at FROM sessions WHERE id = ?");

    // Check statement preparation by trying to bind
    DbError error = stmt.bind_int(1, id);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to prepare statement or bind id: {}", static_cast<int>(error));
        return std::nullopt;
    }

    // Execute statement
    error = stmt.step();
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to execute select statement: {}", static_cast<int>(error));
        return std::nullopt;
    }

    // Check if we have a row
    if (!stmt.has_row())
    {
        return std::nullopt;
    }

    // Create session from result
    Session session;
    session.id         = stmt.column_int(0);
    session.user_id    = stmt.column_int(1);
    session.token      = std::string(stmt.column_text(2));
    session.created_at = std::string(stmt.column_text(3));
    session.expires_at = std::string(stmt.column_text(4));

    return session;
}

std::vector<Session> SessionRepository::findAll()
{
    PreparedStatement stmt(db_, "SELECT id, user_id, token, created_at, expires_at FROM sessions");

    // For SELECT statements without parameters, we can't check preparation with bind
    // Let's just try to execute and see if it works
    std::vector<Session> sessions;

    // Execute statement and iterate through results
    DbError error = stmt.step();
    while (error == DbError::SUCCESS && stmt.has_row())
    {
        Session session;
        session.id         = stmt.column_int(0);
        session.user_id    = stmt.column_int(1);
        session.token      = std::string(stmt.column_text(2));
        session.created_at = std::string(stmt.column_text(3));
        session.expires_at = std::string(stmt.column_text(4));

        sessions.push_back(session);

        // Move to next row
        error = stmt.step();
    }

    if (error != DbError::SUCCESS && error != DbError::DATABASE_ERROR)
    {  // DATABASE_ERROR might be returned when no more rows
        spdlog::error("Failed to execute select all statement: {}", static_cast<int>(error));
    }

    return sessions;
}

std::optional<Session> SessionRepository::findByUserId(int user_id)
{
    PreparedStatement stmt(db_, "SELECT id, user_id, token, created_at, expires_at FROM sessions WHERE user_id = ?");

    // Check statement preparation by trying to bind
    DbError error = stmt.bind_int(1, user_id);
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to prepare statement or bind user_id: {}", static_cast<int>(error));
        return std::nullopt;
    }

    // Execute statement
    error = stmt.step();
    if (error != DbError::SUCCESS)
    {
        spdlog::error("Failed to execute select by user_id statement: {}", static_cast<int>(error));
        return std::nullopt;
    }

    // Check if we have a row
    if (!stmt.has_row())
    {
        return std::nullopt;
    }

    // Create session from result
    Session session;
    session.id         = stmt.column_int(0);
    session.user_id    = stmt.column_int(1);
    session.token      = std::string(stmt.column_text(2));
    session.created_at = std::string(stmt.column_text(3));
    session.expires_at = std::string(stmt.column_text(4));

    return session;
}

std::vector<Session> SessionRepository::findActiveSessions()
{
    // In a real implementation, we would filter by expiration
    // For now, we'll return all sessions
    return findAll();
}

int SessionRepository::createOrReplace(Session& session)
{
    // First try to update the session
    int result = update(session);

    // If update failed (because session doesn't exist), insert it
    if (result == REPO_ERROR_DATABASE)
    {
        return create(session);
    }

    return result;
}